package com.kamikaze.docidset.impl;

import java.io.IOException;
import java.io.Serializable;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

import org.apache.log4j.Logger;
import org.apache.lucene.search.DocIdSet;
import org.apache.lucene.search.DocIdSetIterator;


public class OrDocIdSet extends ImmutableDocSet implements Serializable {
  private static final long serialVersionUID = 1L;

  private static final int INVALID = -1;

  private static transient Logger log = Logger.getLogger(OrDocIdSet.class);

  public class AescDocIdSetComparator implements Comparator<DocIdSetIterator>,
      Serializable {

    private static final long serialVersionUID = 1L;

    public int compare(DocIdSetIterator o1, DocIdSetIterator o2) {
      return o1.doc() - o2.doc();
    }

  }

  List<DocIdSet> sets = null;
  private int nonNullSize; // excludes nulls
  
  private int _size = INVALID;

  

  public OrDocIdSet(List<DocIdSet> docSets) {
    this.sets = docSets;
    int size = 0;
    if (sets != null) {
      for(DocIdSet set : sets) {
        if(set != null) size++;
      }
    }
    nonNullSize = size;
  }

  class OrDocIdSetIterator extends DocIdSetIterator {
    int lastReturn = -1;
    boolean moreDoc = true;
    PriorityQueue<DocIdSetIterator> pq = null;
    
    OrDocIdSetIterator() {
      if (nonNullSize < 1)
        throw new IllegalArgumentException("Minimum one iterator required");      
    }

    private void initialize(int target) throws IOException {
      pq = new PriorityQueue<DocIdSetIterator>(nonNullSize, new AescDocIdSetComparator());
      for (DocIdSet set : sets) {
        if (set != null) {
          DocIdSetIterator dcit = set.iterator();
          if (dcit.skipTo(target)) {
            pq.offer(dcit);
          }
        }
      }
      moreDoc = (pq.size() > 0);
    }

    @Override
    public int doc() {
      return lastReturn;
    }

    @Override
    public boolean next() throws IOException {
      return skipTo(lastReturn + 1);
    }

    @Override
    public boolean skipTo(int target) throws IOException {
      
      if (!moreDoc) return false;
      
      //if (target <= lastReturn) target = lastReturn + 1;
      if (target <= lastReturn) return true;

      if (pq == null) initialize(target);
      
      while (moreDoc) {
        DocIdSetIterator dcit = pq.peek();
        int docid = dcit.doc();
        if (docid < target) {
          dcit = pq.poll();
          if (dcit.skipTo(target)) {
            pq.offer(dcit);
          }
          else {
            moreDoc = (pq.size() > 0);
          }
        }
        else {
          lastReturn = docid;
          break;
        }
      }
      return moreDoc;
    }
  }

  @Override
  public DocIdSetIterator iterator() {
    return new OrDocIdSetIterator();
  }
  
  
  /**
   * Find existence in the set with index
   * 
   * NOTE :  Expensive call. Avoid.
   * @return
   */
  @Override
  public int findWithIndex(int val)
  {
    DocIdSetIterator finder = new OrDocIdSetIterator();
    int cursor = -1;
    try {
      while(finder.next())
      {
        if(finder.doc() > val)
          return -1;
        else if(finder.doc() == val )
          return ++cursor;
        else 
          ++cursor;
        
      
      }
    } catch (IOException e) {
      return -1;
    }
    return -1;
  }

  
  public int size()
  {
  
    if(_size==INVALID)
    {
      _size=0;
      DocIdSetIterator it = this.iterator();
      
      try {
        while(it.next())
          _size++;
      } catch (IOException e) {
        e.printStackTrace();
        _size = INVALID;
      }
      
    }
    return _size;
  }
}
